package org.needcoke.hs.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.needcoke.hs.bean.*;
import org.needcoke.hs.signature.ConfigSignature;
import org.needcoke.hs.signature.Signature;
import org.springframework.boot.CommandLineRunner;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

@Slf4j
@Component
public class HsCommandLineRunner implements CommandLineRunner {


    @Resource
    private InterceptorPropertiesConfig signatureBeanConfig;

    @Resource
    private Map<InterceptRule, Signature> signatureMap;

    @Resource
    private List<ConfigImporter> configImporters;

    private final Map<String, Signature> configNameMap = new HashMap<>();

    @Override
    public void run(String... args) throws Exception {
        configImporters.forEach(ConfigImporter::importConfig);
        List<Config> configList = signatureBeanConfig.getConfig();
        if (CollUtil.isNotEmpty(configList)) {
            configList.forEach(config -> spelRecognizer(Config.class, config));
            for (Config config : configList) {
                Map<String, Signature> stringSignatureMap = conventConfigToSignature(config);
                if (null != stringSignatureMap) {
                    configNameMap.putAll(stringSignatureMap);
                }
            }
        }

        List<SingleUrl> singleUrlList = signatureBeanConfig.getSingleUrl();
        if (CollUtil.isNotEmpty(singleUrlList)) {
            singleUrlList.forEach(singleUrl -> spelRecognizer(SingleUrl.class, singleUrl));
            for (SingleUrl singleUrl : singleUrlList) {
                Map<InterceptRule, Signature> stringSignatureMap = conventSingleUrlToSignature(singleUrl);
                signatureMap.putAll(stringSignatureMap);
            }
        }
        List<UrlGroup> urlGroupList = signatureBeanConfig.getUrlGroup();
        if (CollUtil.isNotEmpty(urlGroupList)) {
            urlGroupList.forEach(urlGroup -> spelRecognizer(UrlGroup.class, urlGroup));
            for (UrlGroup urlGroup : urlGroupList) {
                Map<InterceptRule, Signature> stringSignatureMap = conventUrlGroupToSignature(urlGroup);
                signatureMap.putAll(stringSignatureMap);
            }
        }
    }

    ExpressionParser parser = new SpelExpressionParser();

    TemplateParserContext parserContext = new TemplateParserContext();

    private void spelRecognizer(Class clz, Object obj) {
        Field[] fields = getAllFields(clz);
        for (Field field : fields) {
            if ((!Modifier.isFinal(field.getModifiers())) &&
                    (!Modifier.isStatic(field.getModifiers())) && field.getType().getTypeName().equals(String.class.getTypeName())) {
                try {
                    field.setAccessible(true);
                    String exp = (String) field.get(obj);
                    if (StrUtil.isEmpty(exp)) {
                        continue;
                    }
                    Expression expression = parser.parseExpression(exp, parserContext);
                    Object value = expression.getValue();
                    String set = "";
                    if (value instanceof Date) {
                        set = DateUtil.format((Date) value, DatePattern.NORM_DATETIME_PATTERN);
                    } else {
                        set = String.valueOf(value);
                    }
                    field.set(obj, set);
                } catch (IllegalAccessException e) {
                    log.error("class {} 's field {} catch IllegalAccessException ! maybe not a string .", clz.getTypeName(), field.getName());
                }
            }
        }
    }


    public static Field[] getAllFields(Class clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }


    private String getSPELExpression(String expression) {
        if (expression.startsWith("#{") && expression.endsWith("}")) {
            expression = expression.substring(2, expression.length() - 1);
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(expression);
            Object value = exp.getValue();
            if (value instanceof Date) {
                String format = DateUtil.format((Date) value, DatePattern.NORM_DATETIME_PATTERN);
                log.info("SPEL expression {} , result = {} !", expression, format);
                return format;
            } else {
                String format = String.valueOf(value);
                log.info("SPEL expression {} , result = {} !", expression, format);
                return format;
            }
        } else {
            return expression;
        }
    }


    private Map<InterceptRule, Signature> conventUrlGroupToSignature(UrlGroup urlGroup) {
        if (null == urlGroup) {
            return null;
        }
        Map<InterceptRule, Signature> ret = new HashMap<>();
        if (StrUtil.isNotEmpty(urlGroup.getUseConfigName())) {
            if (!configNameMap.containsKey(urlGroup.getUseConfigName())) {
                log.error("hs url group config name not exist !  --- {}", urlGroup.getUseConfigName());
                throw new RuntimeException("hs single url config name not exist !");
            }
            Signature signature = configNameMap.get(urlGroup.getUseConfigName());

            List<InterceptRule> interceptRuleList = urlGroup.getInterceptRules();
            if (CollUtil.isNotEmpty(interceptRuleList)) {
                for (InterceptRule rule : interceptRuleList) {
                    Signature copy = signature.copy();
                    copy.setInterceptRule(rule);
                    ret.put(rule, copy);
                }
            }
        }
        return ret;
    }


    private Map<String, Signature> conventConfigToSignature(Config config) {
        if (null == config) {
            return null;
        }
        if (StrUtil.isEmpty(config.getName())) {
            log.error("hs config must have a name ! and the config will not in use.");
            return null;
        }
        if (configNameMap.containsKey(config.getName())) {
            log.error("hs config duplicate name !  --- {}", config.getName());
            throw new RuntimeException("hs config duplicate name");
        }
        ConfigSignature signature = new ConfigSignature();
        copy(signature, config);
        Map<String, Signature> sMap = new HashMap<>();
        sMap.put(config.getName(), signature);
        return sMap;
    }


    private Map<InterceptRule, Signature> conventSingleUrlToSignature(SingleUrl singleUrl) {
        if (null == singleUrl) {
            return null;
        }
        if (null == singleUrl.getInterceptRule() || (
                StrUtil.isEmpty(singleUrl.getInterceptRule().getAgreement()) &&
                        StrUtil.isEmpty(singleUrl.getInterceptRule().getHost()) &&
                        StrUtil.isEmpty(singleUrl.getInterceptRule().getPath()) &&
                        StrUtil.isEmpty(singleUrl.getInterceptRule().getPort())
        )) {
            log.error("hs single url must have a url pattern ! and the single url will not in use.");
            return null;
        }
        if (signatureMap.containsKey(singleUrl.getInterceptRule())) {
            log.error("hs single url duplicate url pattern !  --- {}", singleUrl.getInterceptRule());
            throw new RuntimeException("hs single url duplicate url pattern !");
        }
        if (StrUtil.isNotEmpty(singleUrl.getUseConfigName())) {
            if (!configNameMap.containsKey(singleUrl.getUseConfigName())) {
                log.error("hs single url config name not exist ! --- {}  --- {}", singleUrl.getInterceptRule(), singleUrl.getUseConfigName());
                throw new RuntimeException("hs single url config name not exist !");
            }
            Signature cs = configNameMap.get(singleUrl.getUseConfigName());
            Signature copy = cs.copy();
            copy.setInterceptRule(singleUrl.getInterceptRule());
            Map<InterceptRule, Signature> sMap = new HashMap<>();
            sMap.put(singleUrl.getInterceptRule(), copy);
            return sMap;
        }
        ConfigSignature signature = new ConfigSignature();
        copy(signature, singleUrl);
        signature.setInterceptRule(singleUrl.getInterceptRule());
        Map<InterceptRule, Signature> sMap = new HashMap<>();
        sMap.put(singleUrl.getInterceptRule(), signature);
        return sMap;
    }

    private void copy(ConfigSignature signature, BaseConfig config) {
        signature.setConnectorBetweenTwoGroup(config.getConnectorBetweenTwoGroup());
        signature.setConnectorBetweenNameAndValue(config.getConnectorBetweenNameAndValue());
        signature.setConnectorBetweenBodyAndParam(config.getConnectorBetweenBodyAndParam());
        signature.setSignCode(config.getSignCode());
        signature.setProof(config.getProof(), config.isProof_before());
        signature.setBodyBefore(config.getBodyBefore());
        signature.setSignFunction(config.getSignFunction());
        if (StrUtil.isEmpty(config.getSignFunction())) {
            signature.setSignFunction(config.getSignAlgorithm());
        }
        signature.setSortedFunction(config.getSortedFunction());
        signature.setSignatureName(config.getSignatureName());
        signature.setWithFormBody(config.getDoNotWithFormBody());
        signature.setWithApplicationBody(config.getDoNotWithApplicationBody());
        signature.setPrefix(config.getPrefix());
        signature.setSuffix(config.getSuffix());
        signature.setAround(config.getAround());
        signature.setSignKey(config.getSignKey());
        signature.setSignatureLocation(config.getSignatureField());
    }
}
